#ifndef GTL_MEMORY_HEADER_GUARD
#define GTL_MEMORY_HEADER_GUARD

#include "gtlDebug.h"

namespace GTL
{
  namespace Memory
  {

		///************************************************************************/
		///* Memory functions                                                     */
		///************************************************************************/

		inline void* Duplicate(const void* mem, size_t size)
		{
			void* dup = new char[size];
			memcpy(dup,mem,size);
			return dup;
		}

		inline void* Concatenate(const void* mem1, size_t size1, const void* mem2, size_t size2)
		{
			char* dup = new char[size1+size2];
			memcpy(dup,mem1,size1);
			memcpy(dup+size1,mem2,size2);
			return dup;
		}

    ///************************************************************************/
    ///* Array allocation utilities                                           */
    ///************************************************************************/

    template <typename T>
    T** NewArray2D(size_t cx, size_t cy)
    {
      T** a = new T*[cx];
      for (size_t x = 0; x < cx; x++)
        a[x] = new T[cy];
      return a;
    }

    template <typename T>
    void DeleteArray2D(T** a, size_t cx)
    {
      for (size_t x = 0; x < cx; x++)
        delete[] a[x];
      delete[] a;
    }

		///************************************************************************/
		///* Double sided memory allocator                                        */
		///************************************************************************/

    /// TODO FINISH ME
		class DoubleSidedMemoryAllocator
		{
		public:
			DoubleSidedMemoryAllocator(char* mem, size_t size) : mMemory(mem), mMemorySize(size)
			{
				ResetWatermarks();
			}
			DoubleSidedMemoryAllocator(size_t size) : mMemorySize(size), mMemory(new char[size])
			{
				ResetWatermarks();
			}

			char* AllocateHard(size_t bytes); /// permanent memory
			char* AllocateSoft(size_t bytes); /// temporary memory

			void Free(void* ptr);
		protected:
			char* mMemory;
			size_t mMemorySize;

			size_t mHardWatermark, mSoftWatermark;

			void ResetWatermarks()
			{

			}
		};

    ///************************************************************************/
    ///* Memory pool                                                          */
    ///************************************************************************/

    template <typename T>
    class MemoryPool
    {
    public:
      struct Error : public ErrorEnum
      {
        enum
        {
          OK,
          PointerNotInPool,
          PointerAlreadyFree,
          PoolOverflow
        };
      };

      MemoryPool(size_t size)
      {
        if (!size)
        {
          GTL_ASSERT("Wrong memory pool size" && 0);
          return;
        }
        mPool = new T[size];
        mFree = new T*[size];

        if (!mPool || !mFree)
        {
          GTL_ASSERT("Cannot allocate memory" && 0);
          return;
        }

        mObjects = size;

        FreeAll();
      }

      void FreeAll()
      {
        for (size_t i=0; i<mObjects; ++i)
        {
          mFree[i] = &(mPool[(mObjects-i)-1]);
        }
        mTop = mObjects-1;
      }

      T* New()
      {
        if (mTop==0) return 0;
        T* result = mFree[mTop];
        mTop--;
        return result;
      }

      ErrorEnum Free(T* pointer)
      {
        if (pointer < mPool || pointer >= (mPool+mObjects)) return Error::PointerNotInPool;
        if (mTop >= mObjects) return Error::PoolOverflow;

#ifdef GTL_DEBUG
        for (size_t i=0; i<mTop; i++)
        {
          if (mFree[i] == pointer)
            return Error::PointerAlreadyFree;
        }
#endif

        mTop++;
        mFree[mTop] = pointer;

        return Error::OK;
      }
      ~MemoryPool()
      {
        delete[] mPool;
        delete[] mFree;
      }
    private:
      T* mPool;
      T** mFree;
      size_t mObjects;
      size_t mTop;
    };
  }
}

#endif